﻿using System;
using log4net;
using Xtensive.Storage;
using System.Linq;
using Xtensive.Storage.Model;

namespace Sherwood.Content.DO.Model
{
    /// <summary>
    /// A base class for all meta 
    /// </summary>
    [HierarchyRoot]
    [Serializable]
    [Index("Client", "Record", "UsageKey")]
    public abstract class Meta : Entity, IMeta
    {
        [ThreadStatic]
        internal static MetaInstantiationContext Instantiation;

        private static readonly ILog Log = LogManager.GetLogger(typeof(Meta));

        private static MetaInstantiationContext GetInstantiation()
        {
            if(Instantiation == null)
                throw new InvalidOperationException("Parameterless constructor may only be used through CreateNew method on meta repository.");
            return Instantiation;
        }

        protected Meta()
            : this(GetInstantiation().Record, GetInstantiation().Client, GetInstantiation().UsageKey)
        {
        }

        protected Meta(Record record, Client source)
            : this(record, source, null)
        {
        }

        protected Meta(Record record, Client source, string usageKey)
        {
            var conflictingMetas = Query.All<Meta>().Where(m => m.Client == Client && m.Record == record && m.UsageKey == usageKey);
            if (conflictingMetas.Any(meta => meta.TypeId == TypeId))
            {
                var message = string.Format(
                    "Meta of type {0} for record, client and usageKey already exists.", TypeInfo.Name);
                throw new ReferentialConstraintViolationException(message);
            }

            Client = source;
            Record = record;
            UsageKey = usageKey;
            
            DateCreated = DateTime.UtcNow;

            if (Log.IsDebugEnabled)
                Log.DebugFormat("{0} created for record {1}", TypeInfo.Name, record.Id);
        }

        /// <inheritdoc/>
        [Field, Key]
        public Guid Id { get; private set; }

        /// <inheritdoc/>
        [Field(Indexed = true, Length = 1024, Nullable = true)]
        public string UsageKey { get; set; }

        /// <summary>
        /// The Item for which this is a meta part.
        /// </summary>
        [Field]
        public Record Record { get; set; }

        /// <summary>
        /// The source client of this meta item.
        /// </summary>
        [Field]
        public Client Client { get; set; }

        /// <inheritdoc/>
        [Field]
        public DateTime DateCreated { get; private set; }

        /// <inheritdoc/>
        [Field]
        public DateTime DateModified { get; private set; }

        protected override void OnSetFieldValue(FieldInfo field, object oldValue, object newValue)
        {
            if (ShouldUpdateDateModified(field, oldValue, newValue))
                DateModified = DateTime.UtcNow;

            base.OnSetFieldValue(field, oldValue, newValue);
        }

        protected virtual bool ShouldUpdateDateModified(FieldInfo field, object oldValue, object newValue)
        {
            return field.DeclaringType.UnderlyingType.IsSubclassOf(typeof (Meta));
        }

        IRecord IMeta.Record
        {
            get { return Record; }
        }

        IClient IMeta.Client
        {
            get { return Client; }
        }

        protected override void OnRemove()
        {
            base.OnRemove();

            if (Log.IsDebugEnabled)
                Log.DebugFormat("Meta {0} deleted", Id);
        }
    }
}